package com.demo.webapp.filter;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.demo.common.StringUtils;
import com.demo.core.model.Site;
import com.demo.core.service.base.SiteService;
import com.demo.webapp.context.MultisiteContext;
import com.demo.webapp.context.MultisiteContextHolder;
import com.demo.webapp.context.SimpleMultisiteContext;
import com.demo.webapp.exception.SiteAccessException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.filter.OncePerRequestFilter;

/**
 * 多站点过滤器</br>
 * 该过滤器将访问站点信息进行封装后</br>
 * 保存在{@link MultisiteRequestWrapper}中</br>
 * 在取得request时，将是一个MultisiteRequestWrapper对象<br>
 * 同时,将站点信息存入线程变量中,多线程下方便访问
 * 
 * @author ryuu.kk
 *
 */
public class MultisiteFilter extends OncePerRequestFilter {

	/**
	 * 日志
	 */
    private final static transient Log log = LogFactory.getLog(MultisiteFilter.class);
    
	/**
	 * 站点Map缓存
	 */
	private static final Map<String, Site> siteMapCache = new HashMap<String, Site>();
	
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws ServletException, IOException {
			
		/**
		 * 站点模型信息
		 */
		Site site = null;
		
		/* ***************************************************************
		 * 使用url的第一段内容作为站点名:/xxx.com/cms/manage/... 其中cms作为站点名 
		String url = request.getRequestURI();
		String[] urlSplit = url.split("/");
		String siteString = "";
		if (urlSplit.length > 0) {
			if ("".equals(urlSplit[0].trim())) {
				siteString = urlSplit.length > 1 ? urlSplit[1] : "";
			} else {
				siteString = urlSplit[0];
			}
		}
		***************************************************************** */
		//将站点上下文容器放入当前线程中
		MultisiteContext<Site> siteContext = new SimpleMultisiteContext();
		
		// 从session取得站点信息
		HttpSession session = request.getSession(true);
		
		site = MultisiteFilter.createSite(request);
		
		if (site == null) {
			//throw new ServletException("Not Site String in request.");
			if (log.isDebugEnabled()) {
				log.debug("Not Site String in request.");
			}
			chain.doFilter(request, response);
			return;
		}
		siteContext.setMultisite(site);
		// 一些无法得到容器上下文MultisiteContext的模块,需要在Session中取得站点信息
		// 比如Security包,就不依赖Webapp包。
		session.setAttribute(MultisiteContext.MULTISITE_CONTEXT, site);
		
		MultisiteContextHolder.setContext(siteContext);
		MultisiteRequestWrapper wrapper = new MultisiteRequestWrapper(request, site);
		chain.doFilter(wrapper, response);
		//清除线程中的站点上下文
		MultisiteContextHolder.clearContext();
	}
	
	/**
	 * 获得站点模型
	 * @param request HttpServletRequest
	 * @return Site 如果site=null则表示未找到站点。
	 */
	public final static Site createSite(HttpServletRequest request) {
		
		/**
		 * 站点模型信息
		 */
		Site site = null;
		
		/**
		 * Spring Ioc上下文容器
		 */
		ApplicationContext context = null;
		
		/**
		 * 站点服务
		 */
		SiteService siteService = null;
		
		// 从session取得站点信息
		HttpSession session = request.getSession(true);
		
		// 取得session存放的站点信息，
		Site siteCache = null;
		if (session != null) {
			siteCache = (Site) session.getAttribute(MultisiteContext.MULTISITE_CONTEXT);
		}
		// 获取站点域名,如果是localhost或者是127.0.0.1或者是使用ip，则使用项目名或“/”
		String siteString = request.getServerName();
		// 站点域名,通过该域名区分不同站点访问。
		String domain = "";
		
		if (log.isDebugEnabled()) {
			log.debug("Access WebSite ServerName : " + siteString + " and Remote Host is " + request.getRemoteHost() + " and Local Address is " + request.getLocalAddr());
		}
		/* *************************************************************************************
		 *   站点获得规则
		 * 1、使用请求参数domain关键字的内容为域  
		 * 2、如果1不存在,使用域名登陆,获得域名前缀(www.xxx.com的xxx作为域)
		 * 3、如果1,2不存在,使用 ip登陆,则使用web项目名作为域                                 
		 * 4、如果1,2,3不存在,使用ip登陆,且使用ROOT根目录的项目,以'/'作为项目名
		 *************************************************************************************** */
		if ("localhost".equals(siteString) 
				|| "127.0.0.1".equals(siteString)
				|| request.getLocalAddr().equals(siteString)) {
			// 获得路径,按照Servlet路径作为项目名
			String url = request.getContextPath();
			String[] urlSplit = url.split("/");
			// 对项目名进行判断,若使用"/xxx"的项目名,
			if (urlSplit.length > 0) {
				if ("".equals(urlSplit[0].trim())) {
					domain = urlSplit.length > 1 ? urlSplit[1] : "";
				} else {
					domain = urlSplit[0];
				}
				// 如果不存在项目名,则项目存放在根路径ROOT
				if (StringUtils.isEmpty(domain)) {
					// 用户使用ROOT根路径存放项目
					domain = "ROOT";
				}
			}
			if (log.isDebugEnabled()) {
				log.debug("Use IP :[" + request.getServerName() + "] model, Context path(Domain) : " + domain + " SiteString : " + siteString);
			}
		} else {
			// 使用泛域名地址,如果是www,则表示根域名
			domain = siteString.substring(0, siteString.indexOf("."));				
		}
		
		// 找到站点
		String siteName = request.getParameter(MultisiteContext.KEY_DOMAIN);
		// 没有指定站点时,使用默认站点规则.
		if (StringUtils.isEmpty(siteName)) {
			if (log.isDebugEnabled()) {
				log.debug("not found the 'site' parameter in request, use Domain -> " + domain);
			}
			siteName = domain;
		}
		
		log.info("Use Web-Site : " + siteName);
		
		if (siteMapCache.containsKey(siteName)) {
			site = siteMapCache.get(siteName);
		} else {
			synchronized(siteMapCache) {
				context = getApplicationContext(session);
				siteService = context.getBean(SiteService.class);
				site = siteService.findByDomain(siteName);
				// 通过url和session中site为空时
				if (site == null) {
					// 使用session的站点信息,目的是一些指定站点的请求后,其他请求在没有指定站点时,使用默认站点
					site = siteCache;
					if (site == null) {
						String error = "没有找到指定的站点：" + siteName + ", 请确认您的访问地址.如果您是站点管理员,请确定" 
		                        + siteName + "在您的【站点配置管理】-【站点设置】中“站点域名”项是否对应,如果您已经无法登陆了,那么请在登录地址上增加site=域名参数进行登录.";
						//response.sendError(HttpServletResponse.SC_NOT_FOUND, error);
						log.error("unknow site name : " + siteName + " occur this error because site is not equals “domin” in Site model (DOMAIN column IN T_CORE_SITE).");
						// 站点访问异常;
						SiteAccessException e = new SiteAccessException("未知站点：[" + siteName + "], 请查看该域对应的站点Site模型信息.");
						e.setOccurrence(error);
						throw e;
					}
				} else {
					if (!siteMapCache.containsKey(siteName)) {
						siteMapCache.put(siteName, site);
					}
				}
			}
		}
		return site;
	}
	
	
	/**
	 * 清除站点缓存
	 * <p>（变更站点信息时，使用该方法清除站点缓存信息，当用户登录时，将重新加载站点信息）</p>
	 */
	public synchronized static void refresh() {
		
		//Thread.sleep(100);
		if (siteMapCache != null) {
			// 
			siteMapCache.clear();
		}
	}
	
	/**
	 * 取得SpringIoc容器
	 * @return ApplicationContext 上下文容器
	 */
	protected ApplicationContext getApplicationContext() {
	    return WebApplicationContextUtils.getWebApplicationContext(getFilterConfig().getServletContext());
	} 
	
	/**
	 * 取得SpringIoc容器
	 * @return ApplicationContext 上下文容器
	 */
	private static ApplicationContext getApplicationContext(HttpSession session) {
	    return WebApplicationContextUtils.getWebApplicationContext(session.getServletContext());
	} 
}